// Augmented Reality Dynamic Example by Amnon Owed (21/12/11)
// Processing 1.5.1 + NyARToolkit 1.1.6 + GSVideo 1.0

import java.io.*; // for the loadPatternFilenames() function
import processing.opengl.*; // for OPENGL rendering
import jp.nyatla.nyar4psg.*; // the NyARToolkit Processing library
import codeanticode.gsvideo.*; // the GSVideo library

// a central location is used for the camera_para.dat and pattern files, so you don't have to copy them to each individual sketch
// Make sure to change both the camPara and the patternPath String to where the files are on YOUR computer
// the full path to the camera_para.dat file
//String camPara = "C:/Users/mainframe/Documents/Processing/libraries/nyar4psg/data/camera_para.dat";
// the full path to the .patt pattern files
//String patternPath = "C:/Users/mainframe/Documents/Processing/libraries/nyar4psg/patternMaker/examples/ARToolKit_Patterns";

//----- chemin absolu fichier de paramètres de distorsion de la camera ----
//String camParaPath = "/home/hinault/Téléchargements/librairies_processing/nyar4psg-1.1.6/data/camera_para.dat";
String camParaPath = "/home/greg/processing-2.0.3/modes/java/libraries/NyAR4psg/data/camera_para.dat";


//----- chemin absolu fichiers de description des "patterns" ou "markers" ----
String patternPath = "/home/greg/patternMaker/examples/ARToolKit_Patterns";
// l'archive patternMaker est disponible ici : http://www.cs.utah.edu/gdc/projects/augmentedreality/download.html 

// the dimensions at which the AR will take place. with the current library 1280x720 is about the highest possible resolution.
int arWidth = 640;
int arHeight = 360;

int widthCapture=640; 
int heightCapture=480; 

int widthFinal=640; 
int heightFinal=480; 

int fpsCapture=20;

// the number of pattern markers (from the complete list of .patt files) that will be detected, here the first 10 from the list.
int numMarkers = 20;
String[] nameMarkers= new String[numMarkers]; // pour mémoriser le nom des marker

GSCapture cam;
MultiMarker nya;

PShape rocket;

// this is the arraylist that holds all the objects
ArrayList <ARObject> ars = new ArrayList <ARObject> ();

// the maximum rotation speed (x, y, z) at which the RGBCubes will rotate
float mS = 0.2;

void setup() {
  //size(widthCapture, heightCapture, OPENGL); // the sketch will resize correctly, so for example setting it to 1920 x 1080 will work as well
  size(widthFinal, heightFinal, OPENGL); // the sketch will resize correctly, so for example setting it to 1920 x 1080 will work as well
  cam = new GSCapture(this, widthCapture, heightCapture); // initializing the webcam capture at a specific resolution (correct/possible settings depends on YOUR webcam)
  //cam = new GSCapture(this, widthCapture, heightCapture,fpsCapture,"v4l2src","/dev/video1"); // Initialise objet GSCapture désignant webcam

  cam.start(); // start capturing
  noStroke(); // turn off stroke for the rest of this sketch :-)
  // create a new MultiMarker at a specific resolution (arWidth x arHeight), with the default camera calibration and coordinate system
  nya = new MultiMarker(this, arWidth, arHeight, camParaPath, NyAR4PsgConfig.CONFIG_DEFAULT);
  // set the delay after which a lost marker is no longer displayed. by default set to something higher, but here manually set to immediate.
  nya.setLostDelay(1);
  String[] patterns = loadPatternFilenames(patternPath);
  // for the selected number of markers, add the marker for detection
  for (int i=0; i<numMarkers; i++) {
    String nameMarker = "4x4_"+(i+1)+".patt"; //patterns[i];//"4x4_99.patt"; // mémorise le nom du fichier du marker voulu
    nya.addARMarker(patternPath + "/" + nameMarker, 80); // ajoute le fichier de description à l'objet principal de détection AR 
    // and create an ARObject with the corresponding 'ID'
    ars.add(new ARObject(i));
    println ("Fichier chargé : " + nameMarker);
  }
  // set the color range to 1 (instead of 255), saves typing for the coloring of the cube
  colorMode(RGB, 1);
  
  rocket = loadShape("rocket.obj");
}

void draw() {
  long time = System.currentTimeMillis();
  // if there is a cam image coming in...
  if (cam.available()) {
    cam.read(); // read the cam image
    PImage cBig = cam.get();
    cBig.resize(widthFinal, heightFinal);
    background(cBig); // a background call is needed for correct display of the marker results
    //image(cam, 0, 0, width, height); // display the image at the width and height of the sketch window
    // create a copy of the cam image at the resolution of the AR detection (otherwise nya.detect will throw an assertion error!)
    PImage cSmall = cam.get();
    cSmall.resize(arWidth, arHeight);
    nya.detect(cSmall); // detect markers in the image
    // set the AR perspective uniformly, this general point-of-view is the same for all markers
    nya.setARPerspective();
    // run all the ARObjects's in the arraylist => most things are handled inside the ARObject (see the class for more info)
    for (ARObject ar : ars) { ar.run(); }
    // reset to the default perspective
    perspective();
  }
  //println(System.currentTimeMillis() - time);
}

// this function draws the marker coordinates, note that this is completely 2D and based on the AR dimensions (not the final display size)
/*void drawMarkers() {
  // set the text alignment (to the left) and size (small)
  textAlign(LEFT, TOP);
  textSize(10);
  noStroke();

  // scale from AR detection size to sketch display size (changes the display of the coordinates, not the values)
  //scale(displayScale);

  // for all the markers...
  for (int i=0; i<numMarkers; i++) {
    // if the marker does NOT exist (the ! exlamation mark negates it) continue to the next marker, aka do nothing
    if ((!nya.isExistMarker(i))) { 
      continue;
    }
    // the following code is only reached and run if the marker DOES EXIST
    // get the four marker coordinates into an array of 2D PVectors
    PVector[] pos2d = nya.getMarkerVertex2D(i);
    // draw each vector both textually and with a red dot
    for (int j=0; j<pos2d.length; j++) {
      String s = "(" + int(pos2d[j].x) + "," + int(pos2d[j].y) + ")";
      fill(255);
      rect(pos2d[j].x, pos2d[j].y, textWidth(s) + 3, textAscent() + textDescent() + 3);
      fill(0);
      text(s, pos2d[j].x + 2, pos2d[j].y + 2);
      fill(0, 0, 255);
      ellipse(pos2d[j].x, pos2d[j].y, 10, 10);
    }
  }
}

// this function draws correctly placed 3D boxes on top of detected markers
void drawBoxes() {
  // set the AR perspective uniformly, this general point-of-view is the same for all markers
  nya.setARPerspective();
  // set the text alignment (full centered) and size (big)
  textAlign(CENTER, CENTER);
  textSize(20);
  // for all the markers...
  for (int i=0; i<numMarkers; i++) {
    // if the marker does NOT exist (the ! exlamation mark negates it) continue to the next marker, aka do nothing
    if ((!nya.isExistMarker(i))) { 
      continue;
    }
    // the following code is only reached and run if the marker DOES EXIST
    // get the Matrix for this marker and use it (through setMatrix)
    setMatrix(nya.getMarkerMatrix(i));
    scale(1, -1); // turn things upside down to work intuitively for Processing users
    scale(scaler[i]); // scale the box by it's individual scaler
    translate(0, 0, 20); // translate the box by half (20) of it's size (40)
    lights(); // turn on some lights
    stroke(0); // give the box a black stroke
    //fill(colors[i]); // fill the box by it's individual color
    box(40); // the BOX! ;-)
    noLights(); // turn off the lights
    translate(0, 0, 20.1); // translate to just slightly above the box (to prevent OPENGL uglyness)
    noStroke();
    fill(255, 50);
    rect(-20, -20, 40, 40); // display a transparent white rectangle right above the box
    translate(0, 0, 0.1); // translate to just slightly above the rectangle (to prevent OPENGL uglyness)
    //fill(0);
    //text("" + i, -20, -20, 40, 40); // display the ID of the box in black text centered in the rectangle
  }
  // reset to the default perspective
  perspective();
}*/

// this function loads .patt filenames into a list of Strings based on a full path to a directory (relies on java.io)
String[] loadPatternFilenames(String path) {
  File folder = new File(path);
  FilenameFilter pattFilter = new FilenameFilter() {
    public boolean accept(File dir, String name) {
      return name.toLowerCase().endsWith(".patt");
    }
  };
  return folder.list(pattFilter);
}

// class that defines the AROBject, both the AR detection and display are handled inside this class
class ARObject {
  int ID; // keep track of the current the ID of the object (corresponds with the ID i of the marker)
  PVector rot, speed; // in this example the cube has a certain rotation and rotates at a certain speed
  ARObject(int ID) {
    this.ID = ID; // set the ID
    rot = new PVector(random(TWO_PI), random(TWO_PI), random(TWO_PI)); // random x, y, z rotation
    speed = new PVector(random(-mS, mS), random(-mS, mS), random(-mS, mS)); // random x, y, z speed (within maxSpeed boundaries)
  }
  void run() {
    // always keep rotating (even when the marker is NOT detected)
    rot.add(speed);
    // checks the object's corresponding marker through the ID
    // if the marker is found, display the cube
    if (nya.isExistMarker(ID)) { 
      display();
    }
  }
  // the display in this example shows a colored, rotating RGBCube
  void display () {
    // get the Matrix for this marker and use it (through setMatrix)
    setMatrix(nya.getMarkerMatrix(ID));
    scale(1, -1); // turn things upside down to work intuitively for Processing users
    
    translate(0, 0, 0.1); // translate to just slightly above the rectangle (to prevent OPENGL uglyness)
    fill(0);
    text("" + ID, -20, -20, 40, 40); // display the ID of the box in black text centered in the rectangle
    
    // hover the cube a little above the real-world marker image
    translate(0, 0, 30);
    // rotate the cube in 3 dimensions
    rotateX(PI/2);
    //rotateY(rot.y);
    //rotateZ(rot.z);
    // scale - as with the the color range - to save typing with the coordinates (and make it much easier to change the size)
    scale(0.3);
    // a cube made out of 6 quads
    // the 1 range can be used for both the color and the coordinates as a result of color range and scale (see earlier)
    
    shape(rocket);
    /*beginShape(QUADS);
    fill(0, 1, 1); 
    vertex(-1, 1, 1);
    fill(1, 1, 1); 
    vertex( 1, 1, 1);
    fill(1, 0, 1); 
    vertex( 1, -1, 1);
    fill(0, 0, 1); 
    vertex(-1, -1, 1);
    fill(1, 1, 1); 
    vertex( 1, 1, 1);
    fill(1, 1, 0); 
    vertex( 1, 1, -1);
    fill(1, 0, 0); 
    vertex( 1, -1, -1);
    fill(1, 0, 1); 
    vertex( 1, -1, 1);
    fill(1, 1, 0); 
    vertex( 1, 1, -1);
    fill(0, 1, 0); 
    vertex(-1, 1, -1);
    fill(0, 0, 0); 
    vertex(-1, -1, -1);
    fill(1, 0, 0); 
    vertex( 1, -1, -1);
    fill(0, 1, 0); 
    vertex(-1, 1, -1);
    fill(0, 1, 1); 
    vertex(-1, 1, 1);
    fill(0, 0, 1); 
    vertex(-1, -1, 1);
    fill(0, 0, 0); 
    vertex(-1, -1, -1);
    fill(0, 1, 0); 
    vertex(-1, 1, -1);
    fill(1, 1, 0); 
    vertex( 1, 1, -1);
    fill(1, 1, 1); 
    vertex( 1, 1, 1);
    fill(0, 1, 1); 
    vertex(-1, 1, 1);
    fill(0, 0, 0); 
    vertex(-1, -1, -1);
    fill(1, 0, 0); 
    vertex( 1, -1, -1);
    fill(1, 0, 1); 
    vertex( 1, -1, 1);
    fill(0, 0, 1); 
    vertex(-1, -1, 1);
    endShape();*/
  }
}

